﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using StyleMVVM.DependencyInjection;
using StyleMVVM.Logging;
using StyleMVVM.ViewModel;
using StyleMVVM.Data;
#if !DOT_NET
using Windows.Foundation;
using Windows.Foundation.Metadata;
#endif
#if NETFX_CORE
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

#else
using System.Windows;
using System.Windows.Controls;

#endif

namespace StyleMVVM.View.Regions.Adapters
{
	public sealed class PanelAdapter : IRegionAdapter, IDependencyInjectionContainerAware
	{
		private static string supplemental = typeof(PanelAdapter).FullName;
		private IViewBinderService viewBinder;

		public static void RegisterExport(IDependencyInjectionContainer container)
		{
			container.Register(typeof(PanelAdapter)).
			          As(typeof(IRegionAdapter));
		}

		public Type AddapterType
		{
			get { return typeof(Panel); }
		}

		public IDependencyInjectionContainer Container { get; set; }

		public IEnumerable<object> GetCurrentExports(IRegion region, object control)
		{
			List<object> returnValue = new List<object>();

			foreach (UIElement uiElement in ((Panel)control).Children)
			{
				returnValue.Add(uiElement);
			}

			return returnValue;
		}

#if NETFX_CORE
		public IAsyncOperation<bool> Activate(IRegion region, object control, string newExport)
		{
			return InternalActivate(region, (Panel)control, newExport).AsAsyncOperation();
		}

		public IAsyncOperation<bool> Deactivate(IRegion region, object control, string newExport)
		{
			return InternalDeactivate(region, (Panel)control, newExport).AsAsyncOperation();
		}

		public IAsyncOperation<bool> Navigate(IRegion region, object control, string newExport, object navigationParameter)
		{
			return InternalNavigate(region, (Panel)control, newExport, navigationParameter).AsAsyncOperation();
		}

		[DefaultOverload]
		public IAsyncOperation<bool> Add(IRegion region, object control, string newExportName, object navigationParameter)
		{
			return InternalAdd(region, (Panel)control, newExportName, navigationParameter).AsAsyncOperation();
		}

		public IAsyncOperation<bool> Add(IRegion region, object control, object newExport, object navigationParameter)
		{
			return InternalAdd(region, (Panel)control, newExport, navigationParameter).AsAsyncOperation();
		}

		[DefaultOverload]
		public IAsyncOperation<bool> Remove(IRegion region, object control, string oldExportName)
		{
			return InternalRemove(region, (Panel)control, oldExportName).AsAsyncOperation();
		}

		public IAsyncOperation<bool> Remove(IRegion region, object control, object oldExport)
		{
			return InternalRemove(region, (Panel)control, oldExport).AsAsyncOperation();
		}
#else
		public Task<bool> Activate(IRegion region, object control, string newExport)
		{
			return InternalActivate(region, (Panel)control, newExport);
		}

		public Task<bool> Deactivate(IRegion region, object control, string newExport)
		{
			return InternalDeactivate(region, (Panel)control, newExport);
		}

		public Task<bool> Navigate(IRegion region, object control, string newExport, object navigationParameter)
		{
			return InternalNavigate(region, (Panel)control, newExport, navigationParameter);
		}

		[DefaultOverload]
		public Task<bool> Add(IRegion region, object control, string newExportName, object navigationParameter)
		{
			return InternalAdd(region, (Panel)control, newExportName, navigationParameter);
		}

		public Task<bool> Add(IRegion region, object control, object newExport, object navigationParameter)
		{
			return InternalAdd(region, (Panel)control, newExport, navigationParameter);
		}

		[DefaultOverload]
		public Task<bool> Remove(IRegion region, object control, string oldExportName)
		{
			return Remove(region, (Panel)control, oldExportName);
		}

		public Task<bool> Remove(IRegion region, object control, object oldExport)
		{
			return InternalRemove(region, (Panel)control, oldExport);
		}
#endif

		private IViewBinderService ViewBinder
		{
			get
			{
				if (viewBinder == null)
				{
					viewBinder = Container.LocateByType(typeof(IViewBinderService)) as IViewBinderService;
				}

				return viewBinder;
			}
		}

		private async Task<bool> InternalNavigate(IRegion region,
		                                          Panel control,
		                                          string newExport,
		                                          object navigationParameter = null)
		{
			bool returnValue = true;

			for (int i = 0; i < control.Children.Count;)
			{
				FrameworkElement element = control.Children[i] as FrameworkElement;

				if (element != null)
				{
					if (await CallNavigatingFrom(region, element))
					{
						returnValue = false;
						break;
					}
				}

				control.Children.RemoveAt(i);

				if (element != null)
				{
					CallNavigatedFrom(region, element);
				}
			}

			if (returnValue)
			{
				InternalAdd(region, control, newExport, navigationParameter);
			}

			return returnValue;
		}

		private async Task<bool> InternalAdd(IRegion region,
		                                     Panel control,
		                                     string newExportName,
		                                     object navigationParameter = null)
		{
			bool returnValue = false;

			IEnumerable<object> uiElements = Container.LocateAll(newExportName);

			foreach (object o in uiElements)
			{
				UIElement uiElement = o as UIElement;

				if (uiElement != null)
				{
					ViewBinder.Bind(uiElement, navigationParameter);

					BaseRegion.SetExportName(uiElement, newExportName);

					control.Children.Add(uiElement);

					if (uiElement is FrameworkElement)
					{
						CallNavigatedTo(region, uiElement as FrameworkElement, navigationParameter);
					}

					returnValue = true;
				}
			}

			return returnValue;
		}

		private async Task<bool> InternalAdd(IRegion region,
		                                     Panel control,
		                                     object newExport,
		                                     object navigationParameter = null)
		{
			bool returnValue = false;
			IEnumerable addEnumerable = newExport as IEnumerable;

			if (addEnumerable == null)
			{
				addEnumerable = new List<object> { newExport };
			}

			foreach (object o in addEnumerable)
			{
				if (o is UIElement)
				{
					ViewBinder.Bind(o, navigationParameter);

					control.Children.Add(o as UIElement);

					if (o is FrameworkElement)
					{
						CallNavigatedTo(region, o as FrameworkElement, navigationParameter);
					}
				}
			}

			return returnValue;
		}

		private async Task<bool> InternalRemove(IRegion region, Panel control, string oldExportName)
		{
			bool returnValue = false;

			for (int i = 0; i < control.Children.Count;)
			{
				if (BaseRegion.GetExportName(control.Children[i]) == oldExportName)
				{
					FrameworkElement element = control.Children[i] as FrameworkElement;

					if (element != null)
					{
						if (await CallNavigatingFrom(region, element))
						{
							i++;
							continue;
						}
					}

					control.Children.RemoveAt(i);

					if (element != null)
					{
						CallNavigatedFrom(region, element);
					}

					returnValue = true;
				}
				else
				{
					i++;
				}
			}

			return returnValue;
		}

		private async Task<bool> InternalRemove(IRegion region, Panel control, object oldExport)
		{
			bool returnValue = false;
			IEnumerable removeEnumerable = oldExport as IEnumerable;

			if (removeEnumerable != null)
			{
				foreach (var oldUI in removeEnumerable)
				{
					if (oldUI is UIElement)
					{
						control.Children.Remove(oldUI as UIElement);

						if (oldUI is FrameworkElement)
						{
							CallNavigatedFrom(region, oldUI as FrameworkElement);
						}
					}
				}
			}
			else if (oldExport is UIElement)
			{
				control.Children.Remove(oldExport as UIElement);

				if (oldExport is FrameworkElement)
				{
					CallNavigatedFrom(region, oldExport as FrameworkElement);
				}
			}

			return returnValue;
		}

		private async Task<bool> InternalActivate(IRegion region, Panel contorl, string newExport)
		{
			bool returnValue = false;

			foreach (UIElement uiElement in contorl.Children)
			{
				if (BaseRegion.GetExportName(uiElement) == newExport)
				{
					uiElement.Visibility = Visibility.Visible;

					returnValue = true;
				}
			}

			return returnValue;
		}

		private async Task<bool> InternalDeactivate(IRegion region, Panel contorl, string newExport)
		{
			bool returnValue = false;

			foreach (UIElement uiElement in contorl.Children)
			{
				if (BaseRegion.GetExportName(uiElement) == newExport)
				{
					uiElement.Visibility = Visibility.Collapsed;

					returnValue = true;
				}
			}

			return returnValue;
		}

		private async Task<bool> CallNavigatingFrom(IRegion region, FrameworkElement oldExport)
		{
			if (oldExport != null && oldExport.DataContext is INavigationViewModel)
			{
				INavigationViewModel navigationViewModel = oldExport.DataContext as INavigationViewModel;

				StyleNavigatingCancelEventArgs args = new StyleNavigatingCancelEventArgs();

				await navigationViewModel.NavigatingFrom(oldExport, args);

				return args.Cancel;
			}

			return false;
		}

		private void CallNavigatedTo(IRegion region, FrameworkElement newExport, object navigationParameter)
		{
			if (newExport != null)
			{
				IRegionAware regionAware = newExport.DataContext as IRegionAware;

				if (regionAware != null)
				{
					regionAware.Region = region;
				}

				if (newExport.DataContext is INavigationViewModel)
				{
					INavigationViewModel navigationViewModel = newExport.DataContext as INavigationViewModel;

					navigationViewModel.NavigationParameter = navigationParameter;

					navigationViewModel.NavigatedTo(newExport, new StyleNavigationEventArgs());
				}
			}
		}

		private void CallNavigatedFrom(IRegion region, FrameworkElement oldExport)
		{
			if (oldExport != null)
			{
				IRegionAware regionAware = oldExport.DataContext as IRegionAware;

				if (regionAware != null)
				{
					regionAware.Region = region;
				}

				INavigationViewModel navigationViewModel = oldExport.DataContext as INavigationViewModel;

				if (navigationViewModel != null)
				{
					navigationViewModel.NavigatedFrom(oldExport, null);
				}
			}
		}
	}
}